#!/usr/bin/env python3
#
#  Copyright 2016 The TMM/AD Authors. All rights reserved.
#  Use of this source code is governed by a BSD-style license that can be
#  found in the license.txt file.
#

import tmm.web.base.logger as _logger
import tmm.web.mq.subscription as _subscription
import pika as _pika
import time as _time

#  Constants.
_MQ_EXCHANGE_NAME = "TMM_AD_MessagingExchanger"


# noinspection PyUnusedLocal
def _mq_receive_callback(channel, method, properties, body):
    """The callback of receiving a message.

    :type body: bytes
    :param channel: The channel.
    :param method: The method.
    :param properties: The properties.
    :param body: The message body.
    """

    _subscription.publish_message(body)


def messaging_thread_entry(
    host,
    port,
    user,
    pwd,
    vhost,
    heartbeat,
    reconnect_delay,
    timeout
):
    """The entry of the messaging thread.

    :type host: str
    :type port: int
    :type user: str
    :type pwd: str
    :type vhost: str
    :type heartbeat: int
    :type reconnect_delay: int
    :type timeout: int
    :param host: The host of the message queue server.
    :param port: The port of the message queue server.
    :param user: The user name of the authentication credential.
    :param pwd: The password of the authentication credential.
    :param vhost: The virtual host of the message queue server.
    :param heartbeat: The heartbeat interval (in seconds).
    :param reconnect_delay: The reconnection delay (in seconds).
    :param timeout: The TCP timeout of the message queue client (in seconds).
    """

    #  Initialize the restart flag.
    restart = False

    #  Create the credential and the parameters.
    credential = _pika.PlainCredentials(user, pwd)
    parameters = _pika.ConnectionParameters(
        host=host,
        port=port,
        virtual_host=vhost,
        credentials=credential,
        socket_timeout=timeout,
        heartbeat_interval=heartbeat
    )

    while True:
        #  Show the initial message.
        if restart:
            _logger.log_message(
                "Messaging thread: Restarted.",
                level=_logger.LogLevel.INFORMATION
            )
        else:
            _logger.log_message(
                "Messaging thread: Started.",
                level=_logger.LogLevel.INFORMATION
            )

        #  Initialize the connection container.
        connection = None
        channel = None

        try:
            #  Connect.
            connection = _pika.BlockingConnection(parameters)

            #  Acquire a channel.
            channel = connection.channel()

            #  Declare the exchanges.
            channel.exchange_declare(
                exchange=_MQ_EXCHANGE_NAME,
                exchange_type="fanout"
            )

            #  Create the message queue.
            mq = channel.queue_declare(exclusive=True)
            mq_name = mq.method.queue

            #  Bind the message queue to the exchange.
            channel.queue_bind(
                exchange=_MQ_EXCHANGE_NAME,
                queue=mq_name
            )

            #  Create a consumer.
            channel.basic_consume(
                _mq_receive_callback,
                queue=mq_name,
                no_ack=True
            )

            #  Start consuming.
            channel.start_consuming()
        except Exception as error:
            #  Show the error.
            _logger.log_message(
                "Messaging thread: Caught an exception - \"%s\"." % str(error),
                level=_logger.LogLevel.ERROR
            )

            #  Mark the flag.
            restart = True
        finally:
            # noinspection PyBroadException
            try:
                #  Close the channel if possible.
                if channel is not None and channel.is_open:
                    channel.close()

                #  Close the connection if possible.
                if connection is not None and connection.is_open:
                    connection.close()
            except Exception:
                pass

        #  Delay.
        _logger.log_message(
            "Messaging thread: Reconnect after %d second(s)." % reconnect_delay,
            level=_logger.LogLevel.INFORMATION
        )
        _time.sleep(reconnect_delay)
